﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Xml;
using System.IO;
using System.Xml.Schema;

namespace Kokomo.PeInspector.VisualStudioAdapter
{
	/// <summary>
	/// Represents a C# project file.
	/// </summary>
	/// <remarks>
	/// This class has no public constructor.  Instead, use a derived class.
	/// <para>
	/// A project is composed of <see cref="PropertyGroups"/> and various project items,
	/// exposed by <see cref="ReferenceItems"/>, <see cref="CompileItems"/>,
	/// and <see cref="ResourceItems"/>.
	/// </para>
	/// </remarks>
	/// <see cref="ProjectItem"/>
	/// <seealso cref="CSharp2008Project"/>
	/// <seealso cref="CSharp2010Project"/>
	public class CSharpProject
	{
		/// <summary>
		/// Initializes a new instance of <see cref="CSharpProject"/>.
		/// </summary>
		/// <param name="toolsVersion">The value of the <c>ToolsVersion</c> attribute.</param>
		protected CSharpProject(
			string toolsVersion
			)
		{
			this.PropertyGroups = new List<PropertyGroup>();
			this.ReferenceItems = new List<ReferenceItem>();
			this.CompileItems = new List<CompileItem>();
			this.ResourceItems = new List<ResourceItem>();
			this.Imports = new List<Import>();

			this.ToolsVersion = toolsVersion;
			this.DefaultTargets = "Build";
			this.Imports.Add(new Import() { Project = @"$(MSBuildToolsPath)\Microsoft.CSharp.targets" });

			this.ProjectProperties = new BasicPropertyGroup()
			{
				Configuration = "Debug",
				Platform = "AnyCPU",
				ProductVersion = "8.0.30703",
				SchemaVersion = "2.0",
				FileAlignment = 512
			};
			this.ProjectProperties.ConfigurationProperty.Condition = " '$(Configuration)' == '' ";
			this.PropertyGroups.Add(this.ProjectProperties);

			this.DebugBuildProperties = new BuildPropertyGroup()
			{
				Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ",
				DebugSymbols = true,
				DebugType = "full",
				Optimize = false,
				OutputPath = @"bin\Debug\",
				DefineConstants = "DEBUG;TRACE",
				ErrorReport = "prompt",
				WarningLevel = 4
			};
			this.PropertyGroups.Add(this.DebugBuildProperties);
			this.ReleaseBuildProperties = new BuildPropertyGroup()
			{
				Condition = " '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ",
				DebugSymbols = true,
				DebugType = "pdbonly",
				Optimize = true,
				OutputPath = @"bin\Release\",
				DefineConstants = "TRACE",
				ErrorReport = "prompt",
				WarningLevel = 4
			};
			this.PropertyGroups.Add(this.ReleaseBuildProperties);
		}

		/// <summary>
		/// Writes the project to a file.
		/// </summary>
		/// <param name="fileName">The name of the file to write to.</param>
		public void Save(string fileName)
		{
			if (fileName == null) throw new ArgumentNullException("fileName");

			using (StreamWriter writer = new StreamWriter(fileName))
			{
				XmlTextWriter xml = new XmlTextWriter(writer);
				xml.Formatting = Formatting.Indented;
				xml.WriteStartDocument();

				xml.WriteStartElement("Project", "http://schemas.microsoft.com/developer/msbuild/2003");
				xml.WriteAttributeString("ToolsVersion", this.ToolsVersion);
				xml.WriteAttributeString("DefaultTargets", this.DefaultTargets);

				foreach (var propertyGroup in this.PropertyGroups)
				{
					xml.WriteStartElement("PropertyGroup");
					foreach (var property in propertyGroup.Properties)
					{
						property.WriteXml(xml);
					}
					xml.WriteEndElement();
				}

				IEnumerable<ProjectItem>[] itemGroups = new IEnumerable<ProjectItem>[] {
					this.ReferenceItems,
					this.CompileItems,
					this.ResourceItems
				};
				foreach (var itemGroup in itemGroups)
				{
					if (itemGroup.Count() > 0)
					{
						xml.WriteStartElement("ItemGroup");
						foreach (var item in itemGroup)
						{
							item.WriteXml(xml);
						}
						xml.WriteEndElement();
					}
				}

				foreach (var import in this.Imports)
				{
					xml.WriteStartElement("Import");
					xml.WriteAttributeString("Project", import.Project);
					xml.WriteEndElement();
				}

				xml.WriteEndElement();
			}
		}

		/// <summary>
		/// Gets or sets the value of the <c>ToolsVersion</c> attribute.
		/// </summary>
		[XmlAttribute]
		public string ToolsVersion { get; set; }
		/// <summary>
		/// Gets or sets the value of the <c>DefaultTargets</c> attribute.
		/// </summary>
		[XmlAttribute]
		public string DefaultTargets { get; set; }

		/// <summary>
		/// Gets a list of <see cref="PropertyGroup"/>s for the project.
		/// </summary>
		/// <remarks>
		/// Some of these property groups are exposed by other properties,
		/// such as <see cref="ProjectProperties"/>,
		/// <see cref="DebugBuildProperties"/>,
		/// and <see cref="ReleaseBuildProperties"/>.
		/// </remarks>
		[XmlIgnore]
		public List<PropertyGroup> PropertyGroups { get; private set; }
		/// <summary>
		/// Gets the <see cref="BasicPropertyGroup"/> for project properties.
		/// </summary>
		[XmlIgnore]
		public BasicPropertyGroup ProjectProperties { get; private set; }
		/// <summary>
		/// Gets the <see cref="BuildPropertyGroup"/> for the <c>Debug</c> configuration.
		/// </summary>
		[XmlIgnore]
		public BuildPropertyGroup DebugBuildProperties { get; private set; }
		/// <summary>
		/// Gets the <see cref="BuildPropertyGroup"/> for the <c>Release</c> configuration.
		/// </summary>
		[XmlIgnore]
		public BuildPropertyGroup ReleaseBuildProperties { get; private set; }

		/// <summary>
		/// Gets the list of <see cref="ReferenceItem"/>s.
		/// </summary>
		[XmlIgnore]
		public List<ReferenceItem> ReferenceItems { get; private set; }
		/// <summary>
		/// Gets the list of <see cref="CompileItem"/>s.
		/// </summary>
		[XmlIgnore]
		public List<CompileItem> CompileItems { get; private set; }
		/// <summary>
		/// Gets the list of <see cref="ResourceItem"/>s.
		/// </summary>
		[XmlIgnore]
		public List<ResourceItem> ResourceItems { get; private set; }

		/// <summary>
		/// Gets the list of <see cref="Import"/>s.
		/// </summary>
		[XmlIgnore]
		public List<Import> Imports { get; private set; }
	}
}
